package jflowmap.model.ontology;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;

import com.google.common.collect.Sets;

public final class Value {

	private Measurement measurement;
	private Set<Dimension> dimensions = new HashSet<Dimension>();
	
	private final double value;
	private final Context context;
	
	public final static Comparator<Value> COMPARE_VALUE_ONLY = new Comparator<Value>() {
		@Override
		public int compare(Value v1, Value v2) {
			return v1.getValue() > v2.getValue() ? 1
				 : v2.getValue() > v1.getValue() ? -1
				 : 0;
		}
		
	};
	
	public final static Comparator<Value> COMPARE_SAFELY = new Comparator<Value>() {
		@Override
		public int compare(Value v1, Value v2) {
			if (v1.getMeasurement().equals(v2.getMeasurement())) {
				return COMPARE_VALUE_ONLY.compare(v1, v2);
			} else {
				return v1.getMeasurement().toString().compareTo(v2.getMeasurement().toString());
			}
		}
	};
	
	public Value as (final Dimension dimension) {
		this.dimensions.add(dimension);
		dimension.addValue(this);
		return this;
	}
	
	public Value as (final String dimensionName) {
		return this.as(context.getDimension(dimensionName));
	}
	
	public Value in (final String measurementName) {
		this.measurement = context.getMeasurement(measurementName);
		return this;
	}
	
	public Value in (final Measurement measurement) {
		this.measurement = measurement;
		return this;
	}
	
	public double getValue () {
		return value;
	}
	
	public Measurement getMeasurement() {
		return this.measurement;
	}
	
	public Set<Dimension> getDimensions() {
		return Collections.unmodifiableSet(dimensions);
	}

	Value (final double value, final Context context) {
		this.value = value;
		this.context = context;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((context == null) ? 0 : context.hashCode());
		result = prime * result
				+ ((dimensions == null) ? 0 : dimensions.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Value other = (Value) obj;
		if (context == null) {
			if (other.context != null)
				return false;
		} else if (!context.equals(other.context))
			return false;
		if (dimensions == null) {
			if (other.dimensions != null)
				return false;
		} else if (!dimensions.equals(other.dimensions))
			return false;
		return true;
	}

	public Dimension childOfAny (Set<Dimension> aggregateDimensions) {
		for (Dimension d: dimensions) {
			for (Dimension p: aggregateDimensions) {
				if (d.isDirectChildOf(p)) return p;
			}
		}
		return null;
	}
	
	public Dimension partOfAny (Set<Dimension> aggregateDimensions) {
		for (Dimension d: dimensions) {
			for (Dimension p: aggregateDimensions) {
				if (d.isDirectPartOf(p)) return p;
			}
		}
		return null;
	}
	
	public Dimension hasAnyOf (Set<Dimension> dimensions) {
		for (Dimension d: this.dimensions) {
			for (Dimension e: dimensions) {
				if (d.equals(e)) return e;
			}
		}
		return null;
	}

	@Override
	public String toString() {
		return "Value\n[measurement=" + getMeasurement() + ",\ndimensions="
				+ dimensions + ",\nvalue=" + value + "]";
	}
	
	public boolean is(Dimension dimension) {
		for (Dimension d: dimensions) {
			if (d.equals(dimension)) return true;
		}
		return false;
	}

	public boolean isDirectChildOf(Dimension parent) {
		for (Dimension d: dimensions) {
			if (d.isDirectChildOf(parent)) return true;
		}
		return false;
	}

	public boolean isDirectPartOf(Dimension ensemble) {
		for (Dimension d: dimensions) {
			if (d.isDirectPartOf(ensemble)) return true;
		}
		return false;
	}

	public boolean isDeepChildOf(Dimension parent) {
		for (Dimension d: dimensions) {
			if (d.isDeepChildOf(parent)) return true;
		}
		return false;
	}

	public boolean isDeepPartOf(Dimension ensemble) {
		for (Dimension d: dimensions) {
			if (d.isDeepPartOf(ensemble)) return true;
		}
		return false;
	}
	
	public Value getPredecessorIn (Dimension parent) {
		if (parent.isFirst()) return null;
		
		final Match matcher = new Match();
		for (Dimension d: dimensions) {
			if (d.isDirectChildOf(parent)) {
				matcher.add(d.getPredecessor());
			} else {
				matcher.add(d);
			}
		}
		
		for (Value each: context.getValues()) {
			if (matcher.matches(each)) return each;
		}
		
		return null;
	}
	
	public Value getSuccessorIn (Dimension parent) {
		if (parent.isLast()) return null;
		
		final Match matcher = new Match();
		for (Dimension d: dimensions) {
			if (d.isDirectChildOf(parent)) {
				matcher.add(d.getSuccessor());
			} else {
				matcher.add(d);
			}
		}
		
		for (Value each: context.getValues()) {
			if (matcher.matches(each)) return each;
		}
		
		return null;
	}
	
	public Set<Dimension> getSameDimensions (final Value other) {
		return Sets.intersection(dimensions, other.dimensions);
	}
	
	public Set<Dimension> getDifferentDimensions (final Value other) {
		return Sets.union(Sets.difference(dimensions, other.dimensions), 
						  Sets.difference(other.dimensions, dimensions));
	}

}
